package net.oschina.snmputil.snmp;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.AbstractMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;

import net.oschina.snmputil.domain.Triple;
import net.oschina.snmputil.domain.ValueType;
import net.oschina.snmputil.exception.NULLOIDException;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.snmp4j.CommunityTarget;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.event.ResponseEvent;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.BitString;
import org.snmp4j.smi.Counter64;
import org.snmp4j.smi.Gauge32;
import org.snmp4j.smi.GenericAddress;
import org.snmp4j.smi.Integer32;
import org.snmp4j.smi.IpAddress;
import org.snmp4j.smi.Null;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.TimeTicks;
import org.snmp4j.smi.UnsignedInteger32;
import org.snmp4j.smi.Variable;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultTcpTransportMapping;
import org.snmp4j.transport.DefaultUdpTransportMapping;

/**
 * 
 * @version 1.0
 * @author 张大川
 * @since
 */
public class SnmpServiceImpl extends AbstractSnmpService {
    private static final Logger logger = Logger
			.getLogger(SnmpServiceImpl.class);
	private Snmp curSession;
	private Address targetAddress;
	private CommunityTarget target;

	public SnmpServiceImpl(int version, String writeCommity, String readCommity) {
		setCommunity(version, writeCommity, readCommity);
	}

	/**
	 * 
	 * @param ip
	 *            SNMP agent ip
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public void openSession(String ip) throws UnknownHostException, IOException {
		this.host = ip;

		targetAddress = GenericAddress.parse("udp:" + host + "/"
				+ SnmpConstants.DEFAULT_COMMAND_RESPONDER_PORT);

		target = new CommunityTarget();
		if (StringUtils.isNotEmpty(writeCommunity)) {
			target.setCommunity(new OctetString(writeCommunity));
		} else {
			target.setCommunity(new OctetString(readCommunity));
		}
		target.setVersion(this.getSnmpVersion());
		target.setAddress(targetAddress);
		target.setRetries(super.retries);
		target.setTimeout(super.timeout);

		curSession = new Snmp(isUseUdp ? new DefaultUdpTransportMapping()
				: new DefaultTcpTransportMapping());

		curSession.listen();
		logger.debug("open snmp session udp:" + host + "/"
				+ SnmpConstants.DEFAULT_COMMAND_RESPONDER_PORT);

	}

	/**
	 * 
	 * @param ip
	 *            SNMP agent ip
	 * @param port
	 *            RESPONDER PORT
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	public void openSession(String ip, int port) throws UnknownHostException,
			IOException {
		this.host = ip;
		this.port = port;
		targetAddress = GenericAddress.parse("udp:" + host + "/" + port);

		target = new CommunityTarget();
		if (StringUtils.isNotEmpty(writeCommunity)) {
			target.setCommunity(new OctetString(writeCommunity));
		} else {
			target.setCommunity(new OctetString(readCommunity));
		}
		target.setVersion(this.getSnmpVersion());
		target.setAddress(targetAddress);
		target.setRetries(super.retries);
		target.setTimeout(super.timeout);

		curSession = new Snmp(isUseUdp ? new DefaultUdpTransportMapping()
				: new DefaultTcpTransportMapping());

		curSession.listen();
		logger.debug("open snmp session udp:" + host + "/" + port);
	}

	/**
	 * 
	 * @return
	 */
	private int getSnmpVersion() {
		return this.version;
	}
    

	@SuppressWarnings("rawtypes")
	private void processRtn(List<Entry<String, String>> lists,
			Vector variableBindings, String oid) {
		for (int i = 0; i < variableBindings.size(); ++i) {
			VariableBinding vb = (VariableBinding) variableBindings.get(i);
			String key = vb.getOid().toString();
			lists.add(new AbstractMap.SimpleImmutableEntry<String, String>(key
					.replace(oid + ".", ""), vb.getVariable().toString()));
		}
	}

	@Override
	public boolean closeSession() {
		if (curSession == null)
			return true;
		try {
			curSession.close();
			curSession = null;
			return true;
		} catch (Exception ex) {
			return false;
		}
	}

	/**
	 * 
	 * @param oid
	 * @return
	 * @throws IOException
	 */
	public Entry<String, String> getBulk(String oid) throws IOException {
		return getWithIndexImpl(oid, PDU.GETBULK);
	}

	/**
	 * 
	 * @param oid
	 * @return
	 * @throws IOException
	 */
	public Entry<String, String> getWithIndex(String oid) throws IOException {
		return getWithIndexImpl(oid, PDU.GET);
	}

	/**
	 * 根据 OID获取字符串
	 * @param oid
	 * @return 字符串
	 * @throws IOException
	 * @throws NULLOIDException
	 */
	public String get(String oid) throws IOException, NULLOIDException {
		return getImpl(oid, PDU.GET);
	}

	private String getImpl(String oid, int type) throws IOException,
			NULLOIDException {
		target.setCommunity(new OctetString(readCommunity));
		PDU request = new PDU();
		request.setType(type);
		request.add(new VariableBinding(new OID(oid)));
		PDU response = null;
		ResponseEvent re = curSession.send(request, target);
		response = re.getResponse();
		if (response == null) {
			throw new NULLOIDException(oid);

		}
		return response.get(0).getVariable().toString();
	}

	@Override
	public Entry<String, String> getNext(String oid) throws IOException {
		return getWithIndexImpl(oid, PDU.GETNEXT);
	}

	@Override
	public Entry<String, String>[] walk(String oid, String index)
			throws IOException {
		return walkImpl(oid, index, false);
	}

	/**
	 * 
	 * @param oid
	 * @return
	 * @throws IOException
	 */
	public Entry<String, String>[] bulkWalk(int i, String... oid)
			throws IOException {
		return walkImplBulk("", i, oid);
	}

	@Override
	public Entry<String, String>[] bulkWalk(String... oid) throws Exception {
		return walkImplBulk("", 50, oid);
	}

	/**
	 * 
	 * @param oid
	 * @param rootId
	 * @param maxRePetitions
	 *            最大重复数
	 * @return
	 * @throws IOException
	 */
	private Entry<String, String>[] walkImplBulk(String rootId,
			int maxRePetitions, String... oids) throws IOException {
		target.setCommunity(new OctetString(readCommunity));
		PDU request = new PDU();
		request.setType(PDU.GETBULK);

		for (String oid : oids) {
			request.add(new VariableBinding(new OID(oid)));
		}
		request.setMaxRepetitions(maxRePetitions);
		request.setNonRepeaters(0);
		// request.setErrorStatus(0);
		// request.setErrorIndex(0);
		ResponseEvent rspEvt = curSession.send(request, target);
		// System.out.println("xxxxxxxxxxx");
		PDU response = rspEvt.getResponse();
		// System.out.println(response);
		List<Entry<String, String>> lists = new LinkedList<Entry<String, String>>();
		if (null != response && response.getErrorIndex() == PDU.noError
				&& response.getErrorStatus() == PDU.noError) {

			Vector<?> vector = response.getVariableBindings();
			// System.out.println(vector.size());
			Entry<String, String>[] val = new Map.Entry[vector.size()];

			for (Object variable : vector) {
				VariableBinding binding = (VariableBinding) variable;
				// System.out.println(binding.getOid().toString());
				// System.out.println(binding.getVariable().toString());
				lists.add(new AbstractMap.SimpleImmutableEntry<String, String>(
						binding.getOid().toString(), binding.getVariable()
								.toString()));
			}
			return lists.toArray(val);

		}
		return null;
	}

	/**
	 *  walk 获取
	 * @param oid
	 * @return 对象组
	 * @throws IOException
	 */

	public Entry<String, String>[] walk(String oid) throws IOException {
		return walkImpl(oid, "", false);
	}

	private Entry<String, String> getWithIndexImpl(String oid, int type)
			throws IOException {
		target.setCommunity(new OctetString(readCommunity));
		PDU request = new PDU();
		request.setType(type);

		request.add(new VariableBinding(new OID(oid)));
		PDU response = null;
		ResponseEvent re = curSession.send(request, target);
		response = re.getResponse();
		if (response == null) {
			return null;
		}
		String rtn = response.get(0).getVariable().toString();
		String index = response.get(0).getOid().toString()
				.replace(oid + ".", "");
		return new AbstractMap.SimpleEntry<String, String>(index, rtn);
	}

	@SuppressWarnings("unchecked")
	private Entry<String, String>[] walkImpl(String oid, String index,
			boolean isBulkWalk) throws IOException {
		target.setCommunity(new OctetString(readCommunity));
		PDU request = new PDU();
		if (isBulkWalk) {
			System.out.println(isBulkWalk);
			request.setType(PDU.GETBULK);
		} else {
			request.setType(PDU.GETNEXT);
		}
		request.setMaxRepetitions(50);
		request.setNonRepeaters(3);
		request.setErrorStatus(0);
		request.setErrorIndex(0);
		String finalOid = oid;
		if (index != null && index.length() > 0) {
			finalOid += "." + index;
		}
		System.out.println(finalOid);
		request.add(new VariableBinding(new OID(finalOid)));
		PDU response = null;
		List<Entry<String, String>> lists = new LinkedList<Entry<String, String>>();

		FinishAll: while (true) {
			ResponseEvent re = curSession.send(request, target);
			response = re.getResponse();
			if (response == null) {
				break;
			}

			int responseSize = response.size();
			System.out.println(responseSize);
			for (int i = 0; i < responseSize; i++) {
				VariableBinding vb = response.get(i);
				if (!vb.getOid().toString().startsWith(finalOid)) {
					for (int j = i; j < responseSize; j++) {
						try {
							response.remove(i);
						} catch (ArrayIndexOutOfBoundsException e) {

							logger.error(e.getMessage(), e);
						}
					}
					processRtn(lists, response.getVariableBindings(), oid);

					break FinishAll;
				}
			}
			processRtn(lists, response.getVariableBindings(), oid);
			VariableBinding next = response.get(response.size() - 1);
			next.setVariable(new Null());
			request.set(0, next);
			request.setRequestID(new Integer32(0));
		}
		Entry<String, String>[] val = new Map.Entry[lists.size()];
		return lists.toArray(val);
	}

	@Override
	public void set(String oid, Object value, ValueType vt) throws IOException,
			NULLOIDException {
		List<Triple<String, Object, ValueType>> list = new LinkedList<Triple<String, Object, ValueType>>();
		list.add(new Triple<String, Object, ValueType>(oid, value, vt));
		set(list);
	}

	/**
	 * 
	 * @param binds
	 * @return
	 * @throws IOException
	 * @throws NULLOIDException
	 */
	public int set(List<Triple<String, Object, ValueType>> binds)
			throws IOException, NULLOIDException {
		target.setCommunity(new OctetString(writeCommunity));
		PDU request = new PDU();
		request.setType(PDU.SET);
		request.setMaxRepetitions(50);
		request.setNonRepeaters(3);
		request.setErrorStatus(0);
		request.setErrorIndex(0);
		for (int i = 0; i < binds.size(); i++) {
			Triple<String, Object, ValueType> p = binds.get(i);
			String oid = p.getFirst();
			logger.info("set oid =" + oid + ",value="
					+ p.getSecond().toString());
			ValueType type = ValueType.VT_INTEGER;
			try {
				if (p.getThird() != null) {
					type = p.getThird();
				} else {
					type = getValueType(oid);
				}
			} catch (NullPointerException e) {
				request.add(new VariableBinding(new OID(oid), new Integer32(
						Integer.parseInt(p.getSecond().toString()))));
				PDU response = null;
				ResponseEvent re = curSession.send(request, target);
				response = re.getResponse();

				if ((response != null)
						&& (response.getErrorStatus() == PDU.noError)
						&& (!response.get(0).isException())) {
					logger.debug("CREATE___________________________________________OK");
					request = new PDU();
					request.setType(PDU.SET);
					request.setMaxRepetitions(50);
					request.setNonRepeaters(3);
					request.setErrorStatus(0);
					request.setErrorIndex(0);
					continue;
				} else {
					if (response == null) { // timeout
						logger.debug("SNMP超时");
					} else if (response.getErrorStatus() != PDU.noError) {
						logger.debug("错误" + response.getErrorStatus());
					} else {
						logger.debug("其他");
					}
				}
			}
			switch (type) {
			case VT_SPSTRING:
				byte[] bytes = p.getSecond().toString().getBytes("GBK");
				request.add(new VariableBinding(new OID(oid), new OctetString(
						bytes)));
				break;
			case VT_HEXSTRING:
				request.add(new VariableBinding(new OID(oid), OctetString
						.fromHexString(p.getSecond().toString())));
				break;
			case VT_STRING:
				request.add(new VariableBinding(new OID(oid), new OctetString(p
						.getSecond().toString())));
				break;
			case VT_INTEGER:
				request.add(new VariableBinding(new OID(oid), new Integer32(
						Integer.parseInt(p.getSecond().toString()))));
				break;
			case VT_LONG:
				request.add(new VariableBinding(new OID(oid), new Counter64(
						Long.parseLong(p.getSecond().toString()))));
				break;
			case VT_NULLOBJ:
				if (p.getSecond().getClass().equals(Integer.class)) {
					request.add(new VariableBinding(new OID(oid),
							new Integer32(Integer.parseInt(p.getSecond()
									.toString()))));
				} else {
					request.add(new VariableBinding(new OID(oid), new Null()));
				}
				break;
			case VT_BITS:
				request.add(new VariableBinding(new OID(oid), new OctetString(p
						.getSecond().toString())));
				break;
			case VT_IP:
				request.add(new VariableBinding(new OID(oid), new IpAddress(p
						.getSecond().toString())));
				break;
			case VT_GAUGE32:
				request.add(new VariableBinding(new OID(oid), new Gauge32(Long
						.parseLong(p.getSecond().toString()))));
				break;
			case VT_UNSIGNED:
				request.add(new VariableBinding(new OID(oid),
						new UnsignedInteger32(Long.parseLong(p.getSecond()
								.toString()))));
				break;
			default:
				continue;
			}
		}

		PDU response = null;
		ResponseEvent re = curSession.send(request, target);
		response = re.getResponse();

		try {
			if ((response != null)
					&& (response.getErrorStatus() == PDU.noError)
					&& (!response.get(0).isException())) {
				logger.debug("Set____________________________________________OK");
			} else {
				throw new NullPointerException();
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			if ((response != null)
					&& (response.getErrorStatus() == PDU.noError)) {
				logger.debug("Set____________________________________________OK");
			} else {
				throw new NullPointerException();
			}
		} catch (NullPointerException e1) {

			if (response.getErrorStatus() != PDU.noError) {
				logger.info("+++++++++++++" + response.getErrorStatus());
				return response.getErrorStatus();
			} else {
				logger.info("+++++++++++++");
			}
			return -1;
		}
		return 0;
	}

	private ValueType getValueType(String oid) throws IOException,
			NULLOIDException {
		PDU request = new PDU();
		request.setType(PDU.GET);
		request.add(new VariableBinding(new OID(oid)));
		PDU response = null;
		OctetString oCommu = target.getCommunity();
		target.setCommunity(new OctetString(readCommunity));
		ResponseEvent re = curSession.send(request, target);
		response = re.getResponse();
		if (response == null) {
			logger.debug("the oid is " + oid);
			throw new NULLOIDException("OID is" + oid);
		}
		Variable var = response.get(0).getVariable();
		if ("noSuchInstance".equals(var.toString())) {
			throw new NullPointerException();
		}
		if (var.getClass().equals(OctetString.class)) {
			return ValueType.VT_STRING;
		} else if (var.getClass().equals(Integer32.class)) {
			return ValueType.VT_INTEGER;
		} else if (var.getClass().equals(Counter64.class)) {
			return ValueType.VT_LONG;
		} else if (var.getClass().equals(TimeTicks.class)) {
			return ValueType.VT_DATE;
		} else if (var.getClass().equals(BitString.class)) {
			return ValueType.VT_BITS;
		} else if (var.getClass().equals(IpAddress.class)) {
			return ValueType.VT_IP;
		} else if (var.getClass().equals(Gauge32.class)) {
			return ValueType.VT_GAUGE32;
		} else if (var.getClass().equals(UnsignedInteger32.class)) {
			return ValueType.VT_UNSIGNED;
		}
		target.setCommunity(oCommu);
		return ValueType.VT_NULLOBJ;
	}

	@Override
	public CommunityTarget getTarget() {
		return target;
	}

	@Override
	protected void finalize() throws Throwable {
		if (curSession != null) {
			closeSession();
		}
		super.finalize();
	}

	/**
	 * 
	 * @param oid
	 * @return
	 * @throws Exception
	 */
	public Entry<String, String>[] newwalk(String oid) throws Exception {
		// TODO

		target.setCommunity(new OctetString(readCommunity));
		// target.set
		List<Entry<String, String>> lists = new LinkedList<Entry<String, String>>();

		PDU pdu = new PDU();
		OID targetOID = new OID(oid);
		pdu.add(new VariableBinding(targetOID));
		pdu.setMaxRepetitions(50);
		pdu.setNonRepeaters(1);
		boolean finished = false;
		logger.debug("---->  start <----");
		while (!finished) {
			VariableBinding vb = null;
			ResponseEvent respEvent = curSession.getNext(pdu, target);

			PDU response = respEvent.getResponse();

			if (null == response) {
				logger.debug("responsePDU == null");
				finished = true;
				break;
			} else {
				vb = response.get(0);
			}
			// check finish
			finished = checkWalkFinished(targetOID, pdu, vb);
			if (!finished) {
				logger.debug("==== walk each vlaue :");
				logger.debug(vb.getOid() + " = " + vb.getVariable().toString());
				// for (int i = 0; i < variableBindings.size(); ++i) {

				String key = vb.getOid().toString();
				lists.add(new AbstractMap.SimpleImmutableEntry<String, String>(
						key, vb.getVariable().toString()));
				// }
				// System.out.println(b1);
				// byte[] ff= vb.
				// Set up the variable binding for the next entry.
				pdu.setRequestID(new Integer32(0));
				pdu.set(0, vb);
			} else {
				logger.debug("SNMP walk OID has finished.");
				// snmp.close();
			}
		}
		logger.debug("---->  end <----");
		@SuppressWarnings("unchecked")
		Entry<String, String>[] val = new Map.Entry[lists.size()];
		return lists.toArray(val);
	}

	private static boolean checkWalkFinished(OID targetOID, PDU pdu,
			VariableBinding vb) {
		boolean finished = false;
		if (pdu.getErrorStatus() != 0) {
			logger.info("[true] responsePDU.getErrorStatus() != 0 ");
			logger.info(pdu.getErrorStatusText());
			finished = true;
		} else if (vb.getOid() == null) {
			logger.info("[true] vb.getOid() == null");
			finished = true;
		} else if (vb.getOid().size() < targetOID.size()) {
			logger.info("[true] vb.getOid().size() < targetOID.size()");
			finished = true;
		} else if (targetOID.leftMostCompare(targetOID.size(), vb.getOid()) != 0) {
			logger.info("[true] targetOID.leftMostCompare() != 0");
			finished = true;
		} else if (Null.isExceptionSyntax(vb.getVariable().getSyntax())) {
			logger.info("[true] Null.isExceptionSyntax(vb.getVariable().getSyntax())");
			finished = true;
		} else if (vb.getOid().compareTo(targetOID) <= 0) {
			logger.info("[true] Variable received is not "
					+ "lexicographic successor of requested " + "one:");
			logger.info(vb.toString() + " <= " + targetOID);
			finished = true;
		}
		return finished;

	}

	// public static void main(String[] args) throws UnknownHostException,
	// IOException, NULLOIDException {
	// SnmpServiceImpl impl = new SnmpServiceImpl(SnmpConstants.version2c,
	// "private", "public");
	// impl.openSession("192.168.91.15");
	//
	// Entry<String, String>[] entry = impl.bulkWalk(100,
	// "1.3.6.1.2.1.2.2.1.10", "1.3.6.1.2.1.2.2.1.16");
	// System.out.println(entry);
	// for (int i = 0; i < entry.length; i++) {
	// System.out.println(entry[i]);
	// }
	//
	// }

	@Override
	public Entry<String, String> getBulk(String... oids) throws IOException {
		// TODO Auto-generated method stub
		return null;
	}

	public static void main(String[] args) throws UnknownHostException,
			IOException, NULLOIDException {
		SnmpServiceImpl impl = new SnmpServiceImpl(SnmpConstants.version2c,
				"public", "public");
		impl.openSession("192.168.1.1");
		String string = impl.get(SnmpConstants.sysName.toString());
		System.out.println(StringUtils.isNotEmpty(string));
	}

/**
 * 增加了一个奇怪的封装，一次获取多个响应。
 * /
	public Entry<String, String>[] getList(String[] index) throws Exception {
		target.setCommunity(new OctetString(readCommunity));
		PDU request = new PDU();
		request.setType(PDU.GET);
		for (String string : index) {
			System.out.println("xxx" + string);
			request.add(new VariableBinding(new OID(string)));
		}

		PDU response = null;
		ResponseEvent re = curSession.send(request, target);
		response = re.getResponse();
		if (response == null) {
			System.out.println("xxx");

		}
		List<Entry<String, String>> lists = new LinkedList<Entry<String, String>>();
		System.out.println("RESPONSE" + response.size());
		Entry<String, String>[] er = new Map.Entry[response.size()];
		for (int i = 0; i < response.size(); i++) {
			lists.add(new AbstractMap.SimpleImmutableEntry<String, String>(
					response.get(i).getOid().toString(), response.get(i)
							.getVariable().toString()));
		}
		return lists.toArray(er);
	}

}